class SyncTickets
{
    constructor(opitons)
    {
        if (Object.keys(opitons.source).length &&
            Object.keys(opitons.target).length
        ) {
            this.sourceJira = new JiraClient(opitons.source);
            this.targetJira = new JiraClient(opitons.target);
            this.customMapping = new CustomMapping();
            this.logger = new LoggerJs();
        }
    }
    /**
     * 1. get origin ticket data
     * 1. have exist target ticket ?
     * 2. have exist target related ticket ?
     * 3. have exist target ticket but no relationship
     * 4. sync one
     * 5. sync many
     */
    async sync(issueKeyOrId)
    {
        let self = this;
        // input issue need include developer
        //let issueKeyOrId = issue.key;
        // get origin ticket
        let sourceTicket = {};
        //sourceTicket = this.customMapping.convert(issue);
        let sourcePromise = await this.sourceJira.getIssue(issueKeyOrId)
                                                    .then(resp => resp.json())
                                                    .then(function(ticket) {
                                                        // convert ticket data
                                                        sourceTicket = self.customMapping.convert(ticket);
                                                    });
        //*/

        sourceTicket.fields.summary = issueKeyOrId + ' ' + sourceTicket.fields.summary;
        this.logger.info('source Ticket#', sourceTicket);

        // get current source ticket status
        let sourceTicketStatus = sourceTicket.fields.status;
        let supportTicket = await this.createOrUpdate(issueKeyOrId, sourceTicket, 'Support');

        // update status
        // add link
        this.logger.info('support ticket', supportTicket);
        //
        let type = 'Task';
        if (supportTicket.oldType) {
            type = supportTicket.oldType == 'Code change' ? 'Bug' : 'Task';
        }

        // if has links
        this.logger.info('sourceTicket A', sourceTicket);

        // here need input old task value, not new, because current db's data is old have not do update
        let childTicket = await this.createOrUpdate(issueKeyOrId, sourceTicket, type);

        this.logger.info('childTicket', childTicket);
        this.logger.info('has link', supportTicket.fields.issuelinks.length);
        this.logger.info('links', supportTicket.fields.issuelinks);

        if (!supportTicket.fields.issuelinks.length || !childTicket.fields.issuelinks.length) {
            let linkResp = await this.targetJira.createLink(supportTicket.key, childTicket.key);
            this.logger.info('create link', linkResp);
        }

        // update support ticket status
        await this.changeTicketStatus(supportTicket.key, supportTicket.fields.status.name, childTicket.fields.issuetype.name, sourceTicketStatus);

        return childTicket;
    }

    async changeTicketStatus(targetIssueKey, targetIssueStatus, type, origStatus)
    {
        // let type = 'Bug';
        // let origStatus = 'Open';
        // let status = 'Solution Design';
        targetIssueStatus = targetIssueStatus.toLocaleLowerCase().replaceAll(" ","_");
        this.logger.info('targetIssueStatus', targetIssueStatus);

        let workflow = {
            "open":{id:1,lable:"Open",to:{common:4}},
            "requirement_analysis":{id:4,label:"Requirement Analysis", to:{common:711}},
            "start_progress":{id:4,label:"Start Progress", to:{common:711}},
            "solution_design":{id:711,label:"Solution Design", to:{Task:861}},
            "under_support":{id:861,label:"Under Support", to:{Task:{limit:["Done","Ready For Release"],id:891}, Bug:901}},
            "waiting_for_reply_to_customer":{id:891,label:"Waiting for Reply to Customer", to:{Task:881}},
            "resolved":{id:881,lable:"Resolved", to:"end"},
            "reply_to_customer":{id:881, label:"Ready To Customer", to: "end"},
            "back_to_solution":{id:901,label:"Back to Solution", to:{Task:861}},
            "under_development":{id:881, label:"Under Development", to: "end"}
        };
        let workflowMapping = {
            1:"open",
            4:"start_progress",
            711:"solution_design",
            861:"under_support",
            891:"waiting_for_reply_to_customer",
            881:"resolved",
            901:"back_to_solution"
        };

        let next = true;
        let step = workflow[targetIssueStatus];
        this.logger.info('1 step', step);
        do {
            if (step.to.common) {
                // do update transitions
                // update step
                step = workflow[workflowMapping[step.to.common]];
                await this.targetJira.setTransition(targetIssueKey, step.id);
                this.logger.info('common next step', step);
            } else if (step.to[type]) {
                this.logger.info('type step.to[type] step', step.to[type]);
                if (step.to[type].limit) {
                    this.logger.info('orig status', origStatus);
                    next = step.to.Task.limit.includes(origStatus.name);
                    this.logger.info('next step', next);
                    if (next) {
                        // do update transitions
                        // update step
                        step = workflow[workflowMapping[step.to[type].id]];
                        await this.targetJira.setTransition(targetIssueKey, step.id);
                        this.logger.info('type next step', step);
                    } else {
                        next = false;
                    }
                } else {
                    // without limit direct jump
                    this.logger.info('direct next step before', step);
                    step = workflow[workflowMapping[step.to[type]]];
                    await this.targetJira.setTransition(targetIssueKey, step.id);
                    this.logger.info('direct next after step', step);
                    // this.logger.info('direct next after step.to[type]', step.to[type]);

                }
            } else {
                this.logger.info('end next false', step);
                next = false;
            }
        } while(next);
        this.logger.info('finish');
    }

    async createOrUpdate(origIssueKeyOrId, sourceTicket, type)
    {
        let origIssueType = sourceTicket.fields.issuetype.name;
        let origDeveloper = sourceTicket.fields.developer;
        let origStatus = sourceTicket.fields.status;
        if (!type) {
            type = 'Support';
        }

        if (type=='Support') {
            sourceTicket.fields.issuetype.name = "Support";
            if (sourceTicket.fields.customfield_10802) {
                let newType = sourceTicket.fields.customfield_10802.value;
            }
        } else {
            sourceTicket.fields.assignee.name = sourceTicket.fields.developer[0].name;
            // type =  sourceTicket.fields.issuetype.name;
            delete sourceTicket.fields.customfield_10700; // ticket id
            delete sourceTicket.fields.customfield_10800;
            delete sourceTicket.fields.customfield_10801;
            delete sourceTicket.fields.customfield_10802;
            delete sourceTicket.fields.customfield_10803;
        }
        delete sourceTicket.fields.developer;
        delete sourceTicket.fields.status;

        // have exist target ticket
        let targetTickets = await this.hasTicket(this.targetJira, origIssueKeyOrId, sourceTicket.fields.assignee.name, type);
        let openTickets = [];
        let targetTicketKey = '';
        let oldType = '';
        //// if have exist support ticket
        let targetTicket = {};
        this.logger.info('has ticket', targetTicket);
        if (targetTickets.length) {
            targetTickets.forEach(function(ticket){
                if (ticket.fields.status.name!='Done' && ticket.fields.status.name!='Ready To Customer') {
                    openTickets.push(ticket);
                }
            });

            if (openTickets.length) {
                for (let idx in openTickets) {
                    targetTicket = openTickets[idx];
                    targetTicketKey = targetTicket.key;
                    if (type=='Support' && targetTicket.fields.customfield_10802) {
                        oldType = targetTicket.fields.customfield_10802.value;
                    } else {
                        // Task update fixVersion
                        // add exist fixVersion to current update data
                        let fixVersions = sourceTicket.fields.fixVersions;
                        targetTicket.fields.fixVersions.forEach(fixVersion => {
                            fixVersions.push({
                                name: fixVersion.name
                            });
                        });
                        sourceTicket.fields.fixVersions = fixVersions;
                    }
                    // update and update or create child ticket
                    // if status is Done, not update ticket

                    this.logger.info('update ticket type', type);
                    let updateRes = await this.targetJira.updateIssue(targetTicketKey, sourceTicket);
                    if (updateRes.status!=204) {
                        this.logger.error('Update Resp', updateRes);
                    }
                }
            } else {
                this.logger.info('related tickets have Done, can not update.', targetTickets);
            }
        } else {
            // create new
            this.logger.info('create ticket', JSON.stringify(sourceTicket));
            let createResp = await this.targetJira.createIssue(sourceTicket)
                                        .then(resp => resp.json())
                                        .then(function(ticket) {
                                            targetTicket = ticket;
                                        });
            oldType = '';
        }

        let issueResp = await this.targetJira.getIssue(targetTicket.key)
        .then(resp => resp.json())
        .then(function(ticket) {
            targetTicket = ticket;
            if (oldType) {
                targetTicket.oldType = oldType;
            }
        });

        sourceTicket.fields.developer = origDeveloper;
        sourceTicket.fields.status = origStatus;
        sourceTicket.fields.issuetype.name = origIssueType;
        return targetTicket;
    }

    async hasTicket(Jira, issueKeyOrId, developer, type)
    {
        if (!type) {
            type = 'Support';
        } else {    
            if (type != 'Support') {
                type = 'Task,Bug';
            }
        }

        let self = this;
        let targetTickets = [];
        let jql = 'type in (' + type + ') AND (summary ~' + issueKeyOrId + ' AND summary !~ "\\\\[RC\\\\]") AND assignee in("' + developer + '")';
        this.logger.info(jql);
        let targetPromise = await Jira.search(jql)
                                                    .then(resp => resp.json())
                                                    .then(function(result) {
                                                        if (result.total) {
                                                            self.logger.info('##search', result);
                                                            targetTickets = result.issues;
                                                        }
                                                    });
        return targetTickets;
    }
}